{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Using ReliableTool to Generate Sub-Questions (Synchronous Version)\n",
    "This notebook demonstrates how to use the `ReliableTool` synchronously."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import logging\n",
    "import random\n",
    "from typing import Annotated\n",
    "\n",
    "# Import necessary components from autogen\n",
    "from autogen import (\n",
    "    ConversableAgent,\n",
    "    GroupChat,\n",
    "    GroupChatManager,\n",
    "    LLMConfig,\n",
    "    UserProxyAgent,\n",
    "    config_list_from_json,\n",
    ")\n",
    "from autogen.tools.experimental.reliable import ReliableTool\n",
    "\n",
    "# Configure logging\n",
    "# Set level to DEBUG to see more internal details if needed\n",
    "# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')\n",
    "logging.basicConfig(level=logging.INFO, format=\"%(asctime)s [%(levelname)s] %(name)s: %(message)s\")\n",
    "logger = logging.getLogger(__name__)\n",
    "\n",
    "print(\"Successfully imported components from autogen.\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "\n",
    "## 1. Define the Core Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "should_error_for_demo = True\n",
    "\n",
    "\n",
    "def buy_airplane_ticket(\n",
    "    from_location: Annotated[str, \"The departure city.\"],\n",
    "    to_location: Annotated[str, \"The destination city.\"],\n",
    "    date: Annotated[str, \"The date of travel (e.g., 'April 12, 2025').\"],\n",
    ") -> str:\n",
    "    \"\"\"\n",
    "    Simulates buying an airplane ticket.\n",
    "\n",
    "    Args:\n",
    "        from_location: The departure city.\n",
    "        to_location: The destination city.\n",
    "        date: The date of travel.\n",
    "\n",
    "    Returns:\n",
    "        A confirmation string with a random ticket number.\n",
    "    \"\"\"\n",
    "    global should_error_for_demo\n",
    "    if should_error_for_demo:\n",
    "        should_error_for_demo = not should_error_for_demo\n",
    "        return \"ERROR: Something weird happened.\"\n",
    "    logger.info(f\"Attempting to buy ticket: {from_location} -> {to_location} on {date}\")\n",
    "    ticket_number = random.randint(1000, 9999)\n",
    "    confirmation = f\"\"\"Your ticket from {from_location} to {to_location} on {date} has been booked.\n",
    "Your ticket number is {ticket_number}.\n",
    "Please keep this number for future reference.\"\"\"\n",
    "    logger.info(f\"Ticket bought successfully: {ticket_number}\")\n",
    "    return confirmation\n",
    "\n",
    "\n",
    "def cancel_airplane_ticket(ticket_number: Annotated[str, \"The 4-digit ticket number to cancel.\"]) -> str:\n",
    "    \"\"\"\n",
    "    Simulates cancelling an airplane ticket.\n",
    "\n",
    "    Args:\n",
    "        ticket_number: The ticket number to cancel.\n",
    "\n",
    "    Returns:\n",
    "        A cancellation confirmation string.\n",
    "    \"\"\"\n",
    "    logger.info(f\"Attempting to cancel ticket: {ticket_number}\")\n",
    "    if not (isinstance(ticket_number, str) and ticket_number.isdigit() and len(ticket_number) == 4):\n",
    "        logger.warning(f\"Invalid ticket number format for cancellation: {ticket_number}\")\n",
    "        return f\"Error: Invalid ticket number format '{ticket_number}'. Please provide a 4-digit number.\"\n",
    "\n",
    "    confirmation = f\"Your ticket with ticket number {ticket_number} has been successfully canceled.\"\n",
    "    logger.info(f\"Ticket cancelled successfully: {ticket_number}\")\n",
    "    return confirmation"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## 2. Configure LLM and ReliableTool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "llm_config: LLMConfig | None = None\n",
    "try:\n",
    "    config_list = config_list_from_json(\n",
    "        \"OAI_CONFIG_LIST\",\n",
    "    )\n",
    "    llm_config = LLMConfig(\n",
    "        *config_list,\n",
    "        temperature=0.7,\n",
    "    )\n",
    "    print(f\"Using LLM config: {config_list[0].get('base_url', 'Default Endpoint')}, Model: {config_list[0]['model']}\")\n",
    "except Exception as e_config:\n",
    "    print(f\"Error creating LLM config: {e_config}. Tools cannot be initialized.\")\n",
    "    llm_config = None\n",
    "buy_system_message_addition_for_tool_calling = \"\"\"You are an assistant that prepares function calls.\n",
    "The user (the outer agent) has provided the necessary arguments: `from_location`, `to_location`, and `date`.\n",
    "Your task is to invoke the internal tool `buy_airplane_ticket` using these exact arguments.\n",
    "You MUST invoke the `buy_airplane_ticket` tool exactly once per response using the provided arguments.\n",
    "Do not add conversational text. Output only the tool call.\n",
    "\"\"\"\n",
    "\n",
    "# Validator: Instructs the internal LLM on how to validate the result returned by buy_airplane_ticket.\n",
    "buy_system_message_addition_for_result_validation = \"\"\"You are a validation assistant. Your task is to validate the output received from the `buy_airplane_ticket` tool.\n",
    "\n",
    "**Input:** You will receive the `arguments` passed to the tool and the `tool_output` string returned by it.\n",
    "\n",
    "**Validation Criteria:**\n",
    "1.  **Success Indication:** The `tool_output` should contain the phrase \"has been booked\" and \"Your ticket number is\".\n",
    "2.  **Content Presence:** The `tool_output` string should not be empty or indicate an obvious error (unless an error was expected, which is not the case here).\n",
    "3.  **Argument Reflection:** The output should ideally reflect the `from_location`, `to_location`, and `date` from the input `arguments`. (This might be hard for the LLM to perfectly verify from just the args and output string, focus on criteria 1 & 2).\n",
    "\n",
    "Analyze the `tool_output` based on the `arguments`. Respond ONLY with your validation assessment in the required JSON format:\n",
    "{\"validation_result\": boolean, \"justification\": \"string\"}\n",
    "- If criteria 1 and 2 are met, set `validation_result` to `true`.\n",
    "- If any criterion is not met, set `validation_result` to `false` and state the reason (e.g., \"Confirmation phrase missing\", \"Output is empty\", \"Output indicates an error\").\n",
    "Do not add any other text before or after the JSON.\n",
    "\"\"\"\n",
    "\n",
    "reliable_buy_tool: ReliableTool | None = None\n",
    "if llm_config:\n",
    "    try:\n",
    "        reliable_buy_tool = ReliableTool(\n",
    "            name=\"ReliableBuyTicket\",\n",
    "            func_or_tool=buy_airplane_ticket,  # Pass the function itself\n",
    "            description=\"Reliably buys an airplane ticket using provided location, destination, and date, with validation.\",\n",
    "            runner_llm_config=llm_config,\n",
    "            validator_llm_config=llm_config,\n",
    "            system_message_addition_for_tool_calling=buy_system_message_addition_for_tool_calling,\n",
    "            system_message_addition_for_result_validation=buy_system_message_addition_for_result_validation,\n",
    "            ground_truth=[\"####Test ground truth entry####\"],\n",
    "        )\n",
    "        logger.info(\"ReliableTool for buying tickets created successfully.\")\n",
    "    except Exception as e_reliable_init:\n",
    "        logger.error(f\"Error creating ReliableTool for buying tickets: {e_reliable_init}\", exc_info=True)\n",
    "else:\n",
    "    logger.warning(\"LLM Configuration not ready. Cannot create ReliableTool for buying.\")\n",
    "\n",
    "\n",
    "# --- ReliableTool for Cancelling Tickets ---\n",
    "\n",
    "# Runner: Similar to the buy runner, it confirms the call to the internal function.\n",
    "cancel_system_message_addition_for_tool_calling = \"\"\"You are an assistant that prepares function calls.\n",
    "The user (the outer agent) has provided the necessary argument: `ticket_number`.\n",
    "Your task is to invoke the internal tool `cancel_airplane_ticket` using this exact argument.\n",
    "You MUST invoke the `cancel_airplane_ticket` tool exactly once per response using the provided argument.\n",
    "Do not add conversational text. Output only the tool call.\n",
    "\"\"\"\n",
    "\n",
    "# Validator: Validates the result of cancel_airplane_ticket.\n",
    "cancel_system_message_addition_for_result_validation = \"\"\"You are a validation assistant. Your task is to validate the output received from the `cancel_airplane_ticket` tool.\n",
    "\n",
    "**Input:** You will receive the `arguments` passed to the tool (containing `ticket_number`) and the `tool_output` string returned by it.\n",
    "\n",
    "**Validation Criteria:**\n",
    "1.  **Success Indication:** The `tool_output` should contain the phrase \"has been successfully canceled\".\n",
    "2.  **Error Handling:** If the `tool_output` contains \"Error: Invalid ticket number format\", the function call technically worked but failed validation, so validation should be `false`.\n",
    "3.  **Content Presence:** The `tool_output` string should not be empty.\n",
    "\n",
    "Analyze the `tool_output` based on the `arguments`. Respond ONLY with your validation assessment in the required JSON format:\n",
    "{\"validation_result\": boolean, \"justification\": \"string\"}\n",
    "- If criteria 1 and 3 are met, and criterion 2 is NOT met, set `validation_result` to `true`.\n",
    "- Otherwise, set `validation_result` to `false` and state the reason (e.g., \"Cancellation confirmation missing\", \"Output is empty\", \"Output indicates invalid ticket number error\").\n",
    "Do not add any other text before or after the JSON.\n",
    "\"\"\"\n",
    "\n",
    "reliable_cancel_tool: ReliableTool | None = None\n",
    "if llm_config:\n",
    "    try:\n",
    "        reliable_cancel_tool = ReliableTool(\n",
    "            name=\"ReliableCancelTicket\",\n",
    "            func_or_tool=cancel_airplane_ticket,  # Pass the function itself\n",
    "            description=\"Reliably cancels an airplane ticket using the provided ticket number, with validation.\",\n",
    "            runner_llm_config=llm_config,\n",
    "            validator_llm_config=llm_config,\n",
    "            system_message_addition_for_tool_calling=cancel_system_message_addition_for_tool_calling,\n",
    "            system_message_addition_for_result_validation=cancel_system_message_addition_for_result_validation,\n",
    "        )\n",
    "        logger.info(\"ReliableTool for cancelling tickets created successfully.\")\n",
    "    except Exception as e_reliable_init:\n",
    "        logger.error(f\"Error creating ReliableTool for cancelling tickets: {e_reliable_init}\", exc_info=True)\n",
    "else:\n",
    "    logger.warning(\"LLM Configuration not ready. Cannot create ReliableTool for cancelling.\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "# Define agents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "if not llm_config:\n",
    "    raise ValueError(\"LLM Config is not set up. Cannot proceed.\")\n",
    "\n",
    "sales_agent = ConversableAgent(\n",
    "    name=\"SalesAgent\",\n",
    "    system_message=\"You are a sales agent responsible for helping customers buy airplane tickets. \"\n",
    "    \"Use the 'ReliableBuyTicket' tool when a user wants to buy a ticket. \"\n",
    "    \"Extract the from_location, to_location, and date from the user's request for the tool.\",\n",
    "    llm_config=llm_config,\n",
    ")\n",
    "\n",
    "cancellation_agent = ConversableAgent(\n",
    "    name=\"CancellationAgent\",\n",
    "    system_message=\"You are a cancellation agent responsible for helping customers cancel airplane tickets. \"\n",
    "    \"Use the 'ReliableCancelTicket' tool when a user wants to cancel a ticket. \"\n",
    "    \"Extract the ticket_number from the user's request for the tool.\",\n",
    "    llm_config=llm_config,\n",
    ")\n",
    "\n",
    "user_proxy = UserProxyAgent(\n",
    "    name=\"UserProxy\",\n",
    "    human_input_mode=\"ALWAYS\",  # Ask user for confirmation before executing any tool\n",
    "    code_execution_config=False,  # Disable code execution for safety\n",
    ")\n",
    "if reliable_buy_tool and reliable_cancel_tool and llm_config:\n",
    "    reliable_buy_tool.register_tool(sales_agent)\n",
    "    reliable_cancel_tool.register_tool(cancellation_agent)\n",
    "    logger.info(\"Registered ReliableTools using agent.register_tools method.\")\n",
    "else:\n",
    "    logger.error(\"One or more ReliableTools are not available for registration.\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## 3. Get User Input and Run the Tool Synchronously"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "if not reliable_buy_tool or not reliable_cancel_tool:\n",
    "    print(\"\\nSkipping chat initiation because one or more ReliableTools failed to initialize.\")\n",
    "else:\n",
    "    groupchat = GroupChat(\n",
    "        agents=[user_proxy, cancellation_agent, sales_agent],\n",
    "        messages=[],\n",
    "        speaker_selection_method=\"auto\",  # LLM decides who speaks next\n",
    "        allow_repeat_speaker=False,  # Prevent back-and-forth loops between two agents\n",
    "    )\n",
    "\n",
    "    manager = GroupChatManager(\n",
    "        name=\"GroupManager\",\n",
    "        groupchat=groupchat,\n",
    "        llm_config=llm_config,\n",
    "        # My local qwen3 needs this so it doesn't get chatty when figuring out who to pick\n",
    "        system_message=\"/no_think You are the manager of a chat between a user, a SalesAgent, and a CancellationAgent. \"\n",
    "        \"Coordinate the conversation. If the user wants to buy a ticket, ensure the SalesAgent responds. \"\n",
    "        \"If the user wants to cancel a ticket, ensure the CancellationAgent responds. \"\n",
    "        \"The UserProxy executes tools after confirmation.\",\n",
    "    )\n",
    "    print(\"\\n\\n--- Initiating Chat: Buy Ticket ---\")\n",
    "    user_proxy.initiate_chat(\n",
    "        recipient=manager,\n",
    "        message=\"Hi, I need to buy a plane ticket from London Heathrow to Tokyo Haneda for May 1st, 2026.\",\n",
    "        clear_history=True,\n",
    "    )"
   ]
  }
 ],
 "metadata": {
  "front_matter": {
   "description": "ReliableTool - Group Chat Example",
   "tags": [
    "tool calling",
    "tools",
    "reliable"
   ]
  },
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
